package com.cnct.generic.dubbo.holder;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.cnct.generic.dubbo.handler.DubboInvokeCoreHandler;
import com.cnct.generic.dubbo.properties.GenericDubboProperties;
import com.cnct.generic.dubbo.strategy.RequireReferenceStrategy;
import com.cnct.generic.dubbo.utils.PackageClassUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static java.lang.Class.forName;


/**
 * 服务提供者的管理
 *
 * @author wangzhen
 */
@Slf4j
@Component
public class ReferenceHolder implements InitializingBean {

    public static final String DIRECT = "direct";

    public static final String REGISTRY = "registry";

    private static final Cache<Object, ReferenceConfig> referenceCache = CacheBuilder.newBuilder()
            .maximumSize(100000)
            .expireAfterWrite(2, TimeUnit.DAYS)
            .build();

    @Autowired
    private Map<String, RequireReferenceStrategy> referenceStrategy = new HashMap<>();

    @Autowired
    private RegistryHolder registryHolder;

    @Autowired
    private GenericDubboProperties properties;

    public Cache<Object, ReferenceConfig> cache() {
        return referenceCache;
    }

    /**
     * 从缓存中移除ReferenceConfig
     *
     * @param key 键
     */
    public void removeReferenceConfig(String key) {
        referenceCache.invalidate(key);
    }


    public ReferenceConfig buildReferenceConfig(ApplicationConfig application, RegistryConfig registry, String group, String version, String interfaceName) {

        ReferenceConfig referenceConfig = new ReferenceConfig<>();
        referenceConfig.setApplication(application);
        referenceConfig.setRegistry(registry);
        referenceConfig.setTimeout(80000);
        referenceConfig.setGeneric(true);
        //获取接口实例
        Class interfaceClass = null;
        try {
            interfaceClass = forName(interfaceName);
        } catch (ClassNotFoundException e) {
            log.error("referenceConfig() execute fail", e);
        }
        referenceConfig.setInterface(interfaceClass);

        if (StringUtils.isNotEmpty(group)) {
            referenceConfig.setGroup(group);
        }

        if (StringUtils.isNotEmpty(version)) {
            referenceConfig.setVersion(version);
        }

        return referenceConfig;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        String scanPackages = properties.getScanPackage();
        if (StringUtils.isBlank(scanPackages)) {
            log.debug("用户未配置包扫描");
            return;
        }

        if (StringUtils.contains(scanPackages, ",")) {

            String[] packages = StringUtils.split(scanPackages, ",");

            for (String scanPackage : packages) {

                prestrainReference(scanPackage);
            }
        } else {
            prestrainReference(scanPackages);

        }
    }

    /**
     * 预加载提供者信息
     *
     * @param scanPackage 扫描的包
     */
    private void prestrainReference(String scanPackage) {
        new Thread(() -> {
            Set<Class<?>> classes = PackageClassUtils.getClasses(scanPackage);
            for (Class<?> scanClass : classes) {
                if (StringUtils.isBlank(scanClass.getName())) {
                    continue;
                }
                ReferenceConfig referenceConfig = referenceStrategy.get(ReferenceHolder.REGISTRY + RequireReferenceStrategy.class.getSimpleName()).
                        requireReferenceConfig(DubboInvokeCoreHandler.application,
                                registryHolder.requireRegistryConfig(properties.getRegistryAddress()),
                                null, null, scanClass.getName(), null);
                referenceConfig.get();
            }
        }).start();
    }
}
